<?php
/*
 * TODO-list:
 *
 * Как инициализировать sql:
 * 0. до кода
 * 1. lazy (не имплементировано)
 * 2. форсированно в коде
 *
 * Как реагировать на ошибки подключения sql (не имплементировано):
 * 0. "Site pok-pok-pok down"
 * 1. Запуск особой страницы
 * 2. никак
 *
 * Как реагировать на ошибки исполнения sql (не имплементировано):
 * 0. exit
 * 1. raise Exception (с возможностью переопределения catch)
 * 2. никак
 *
 * Транзакции:
 * false. Не устанавливаются автоматом
 * true.  Устанавливаются автоматом
 *
 * Можно выбирать уровень транзакций по умолчанию
 *
 * Возможность установить template, а потом заместить там {#main#} на ob_get_content
 */

 class AscetCMSEngine{
  const version='0.5.0';
  var $profiling_stamps;       // Точки профилирования
  var $options;                // Опции при загрузке класса
  var $database_connection;    // AscetPDO connection
  var $storage;    // Key Value Storage
  var $prepared;   // Подготовленные переменные (wait/prepared)
  var $source_file;// Файл с будущим подгруженным кодом, который надо запустить
  var $http_status;// HTTP-статус, возвращённый подгруженным кодом
  var $etag_hash;  // E-Tag Hash
  var $need_prepare_release;   // Если выставлено в true, то над выводом будут произведены функции замены
  var $output_buffer;
  var $is_hhvm;                // Мы под хип-хопом?
  var $real_run_ended;         // null - real run не запущен, false - real run не остановился, true - завершился

  var $error_handled;          // Список отловленных ошибок
  var $error_reporting_level;  // Текущий error_reporting внутри скрипта
  var $error_display;          // Показываем ли мы ошибки

  var $need_commit;            // Нужно коммиттить

  static $last_created_instance=null;

  // Блок констант
  const DATABASE_CONNECTION_STRATEGY_BEFORE       =0;
  const DATABASE_CONNECTION_STRATEGY_LAZY         =1;// не имплементировано
  const DATABASE_CONNECTION_STRATEGY_INCODE       =2;// не имплементировано
  const DATABASE_CONNECTION_ERROR_STRATEGY_MESSAGE=0;
  const DATABASE_CONNECTION_ERROR_STRATEGY_PAGE   =1;// не имплементировано
  const DATABASE_CONNECTION_ERROR_STRATEGY_NOTHING=2;
  const DATABASE_ERROR_STRATEGY_EXIT              =0;
  const DATABASE_ERROR_STRATEGY_EXCEPTION         =1;
  const DATABASE_ERROR_STRATEGY_NOTHING           =2;
  const DATABASE_ERROR_STRATEGY_WARNING           =3;
  const EXECUTION_ERROR_STRATEGY_SILENCE_EXIT     =0;
  const EXECUTION_ERROR_STRATEGY_MESSAGE          =1;
  const EXECUTION_ERROR_STRATEGY_PAGE             =2;// не имплементировано

  function __construct($first_profiling_stamp=null){
   if ($first_profiling_stamp==null){
    $first_profiling_stamp=microtime(true);
   }
   if (self::$last_created_instance!=null){
    // @hint Да, пока концептуально это Singleton
    return;
   }
   
   $this->profiling_stamps=(object)array(
    'init'         =>$first_profiling_stamp,
    'constructed'  =>microtime(true)
   );
   // Выставляем дефолтные значения
   $this->options=(object)array(
    //'current_id'   =>null,                           // for buttons and {#property
    'hidewtime'    =>true, // hide run  time in the of page; May be set in config.php
    'hidetimestamp'=>true, // hide timestamp in the of page; May be set in config.php
    'etag_cache'   =>true, // E-tag/Last-modified; May be set in config.php
    'content_type' =>null,

    'in_admin'     =>false,// true if index.php included from administration panel
    'include_only' =>false,// true if index.php included from some non-AscetCMS place as library with functions
    'hide_cms'     =>false,

    'database_connection_strategy'      =>self::DATABASE_CONNECTION_STRATEGY_BEFORE,       // Как инициализировать SQL
    'database_connection_error_strategy'=>self::DATABASE_CONNECTION_ERROR_STRATEGY_MESSAGE,// Как на ошибки подключения sql
    'database_error_strategy'           =>self::DATABASE_ERROR_STRATEGY_EXCEPTION,         // Как на ошибки исполнения sql
    'database_transaction_start'        =>true,            // Запускать ли транзакции автоматом
    'database_transaction_level'        =>'read committed',// Уровень изоляции транзакций

    'error_reporting_level'             =>E_ALL,
    'error_display'                     =>false,
    'execution_error_strategy'          =>self::EXECUTION_ERROR_STRATEGY_MESSAGE,
    'error_log_file'                    =>$_SERVER['DOCUMENT_ROOT'].'/error.log',

    'keyvalue_storage_engine'           =>'',
    'keyvalue_storage_settings'         =>null,
   );

   self::$last_created_instance=$this;
//   $this->content_size=null;
  }
  
  function init($sad_options){
   $this->profiling_stamps->init_start=microtime(true);
   
   //preset $sad_
   foreach (array('hidewtime', 'hidetimestamp', 'etag_cache', 'in_admin', 'include_only', 'hide_cms',
            'database_connection_strategy', 'database_connection_error_strategy',
            'database_error_strategy', 'database_transaction_start', 'database', 'database_transaction_level',
            'error_reporting_level', 'error_display', 'execution_error_strategy', 'error_log_file',
            'keyvalue_storage_engine', 'keyvalue_storage_settings') as $key){
    if (isset($sad_options->{$key})){
	   $this->options->{$key}=$sad_options->{$key};
    }
   }

   // Проверяем под хипхопом ли мы
   $this->is_hhvm=false;
   if (isset($_ENV['HHVM'])){
    $this->is_hhvm=($_ENV['HHVM']==1);
   }

   // Переопределять суперглобал это просто и понятно!
   $_SERVER['REQUEST_URI']=preg_replace('|/{2,}|','/',$_SERVER['REQUEST_URI']);
   if (!$this->options->in_admin){
    $this->options->root   =preg_replace('|/[^/]+$|','',$_SERVER['SCRIPT_FILENAME']);// document root of cms e.g. /var/www/site
    $this->options->baseurl=preg_replace('|/[^/]+$|','',$_SERVER['SCRIPT_NAME']);    // URL root of cms e.g. /cms
   }else{
    // @hint Да, пока нет никакой админки
    $this->options->root   =preg_replace('|(.*)/i/.*$|','\1',$_SERVER['SCRIPT_FILENAME']);
    $this->options->baseurl=preg_replace('|(.*)/i/.*$|','\1',$_SERVER['SCRIPT_NAME']);
   }
   
   // Подгружаем модули
   // require_once($this->options->root.'/functions/template_engine.php');
   foreach ($sad_options->modules_list as $module){
    require_once($this->options->root.'/functions/'.$module);
   }
   
   // @todo переписать эту строку
   if (strpos(' '.$_SERVER['SCRIPT_FILENAME'],$_SERVER['DOCUMENT_ROOT'].'/')==1){
    $this->options->rurl=preg_replace('|\\?.*$|','',$_SERVER['REQUEST_URI']);            // request URL e.g. /cms/script.php
    $this->options->purl=substr($_SERVER['REQUEST_URI'],strlen($this->options->baseurl));// param URL   e.g. /script.php?param
    $this->options->url =substr($this->options->rurl,strlen($this->options->baseurl));   // TRUE URL without params e.g. /script.php
                                                                   // Try to use $sad_url instead of the others
   }else{
    //AliasMatch in .htaccess
    header('http/1.1 501');
    echo 'Alias Match error';
    return;
   }
   
   // content-type in headers
   $this->options->content_type='';
   if (!preg_match('|\\.([a-z0-9]+)$|', $this->options->url, $sad_t1)){
    $sad_t1=array('','');
   }
   switch(strtolower($sad_t1[1])){
    case 'css': $this->options->content_type='text/css; charset=utf-8';break;
    case 'js':  $this->options->content_type='application/javascript; charset=utf-8';break;
    case 'txt': $this->options->content_type='text/plain; charset=utf-8';break;
    case 'jpg':case 'jpeg': $this->options->content_type='image/jpeg';break;
    case 'png': $this->options->content_type='image/png';break;
    case 'gif': $this->options->content_type='image/gif';break;
    case 'ico': $this->options->content_type='image/vnd.microsoft.icon';break;
    default:    $this->options->content_type='text/html; charset=utf-8';
   }
   if ($this->options->content_type!='text/html; charset=utf-8'){
    $this->options->hidetimestamp=false;
    $this->options->hidewtime    =false;
   }
   unset($sad_t1);


   // @todo Проверить это на...
   $this->options->protocol='http';
   if (isset($_SERVER['HTTPS'])){if ($_SERVER['HTTPS']=='on'){$this->options->protocol='https';}}
   
   $this->options->domain=sad_safe_domain($_SERVER['HTTP_HOST']);
   if (function_exists('mb_internal_encoding')){
    mb_internal_encoding('UTF-8');
   }

   // Выставляем правильный Error Level, а также обработчик ошибок, которого в HHVM нет
   $this->error_reporting($this->options->error_reporting_level, $this->options->error_display);
   set_error_handler(array($this, 'default_error_handler'));
   $this->error_handled=array();

   // Запускаем KeyValue Engine
   if ($this->options->keyvalue_storage_engine!=''){
    $f1='KeyValueStorage'.$this->options->keyvalue_storage_engine;
    $this->storage=new $f1($this->options->keyvalue_storage_settings);
    unset($f1);
   }

   // Выставляем последние значения
   $this->http_status  =200;
   $this->etag_hash    ='';
   $this->prepared     =array();
   $this->profiling_stamps->init_end=microtime(true);
  }// init
  
  function run(){
   if ($this->options->include_only or $this->options->in_admin){return;}
   //CHANGE URL IF NEED
   $sad_url    =$this->options->url;
   include($this->options->root.'/htaccess.php');
   $sad_url    =preg_replace('_/{2,}_', '/', $sad_url);
   if ($sad_url[0]!='/'){$sad_url='/'.$sad_url;}
   // Выбираем путь до файла
   $this->source_file =preg_replace('_/$_', '/index.php', $sad_url);
   unset($sad_url);
   if (!file_exists($this->options->root.'/templates/static/'.$this->source_file)){
    // @todo Определить поведение через options. Вставлять "стандартный" файл ошибок
    header('http/1.1 404');
    echo 'default 404 page. File not found';
    return;
   }
   
   // @todo Определить поведение через options
   if (!preg_match('_/$_',$this->source_file) and is_dir($this->options->root.'/templates/static/'.$this->source_file)){
    header('http/1.1 301 goto');
    header('location: '.$this->options->baseurl.$this->source_file.'/');
    return;
   }

   // Подключение к базе данных, если необходимо
   if ($this->options->database_connection_strategy==self::DATABASE_CONNECTION_STRATEGY_BEFORE){
    $this->profiling_stamps->sql_connect_start=microtime(true);
    $this->database_connect();
    $this->profiling_stamps->sql_connect_end  =microtime(true);
   }

   register_shutdown_function(array($this, 'unexpected_shutdown_handler'));
   // Настоящий запуск
   $sad_need_output     =true;
   $this->real_run_ended=false;
   $this->need_commit   =true;
   $this->profiling_stamps->real_run_start=microtime(true);
   try{
    $this->real_run();
    // @todo определять подключены ли мы сейчас к Базе Данных
    if ($this->need_commit){
     $this->database_connection->commit();
    }else{
     $this->database_connection->rollBack();
    }
   }catch(Exception $e){
    $this->ob_end();
    $sad_need_output=false;
    try{
     $this->database_connection->rollBack();
    }catch(PDOException $e1){}

    header('http/1.1 500');
    switch ($this->options->execution_error_strategy){
     case self::EXECUTION_ERROR_STRATEGY_MESSAGE:     echo $e->getMessage(); break;
     case self::EXECUTION_ERROR_STRATEGY_SILENCE_EXIT:break;
    }
   }finally{
    $this->real_run_ended=true;
   }
   $this->profiling_stamps->real_run_end  =microtime(true);

   // @todo Определять нужно ли добавлять профайлинг — через настройки
   if ($this->need_prepare_release and true){
    $last_time = null;
    foreach($this->profiling_stamps as $key => $value){
     header('X-profiling-'.$key.': '.$value.(($last_time!=null) ? ', '.($value - $last_time) : ''));
     $last_time = $value;
    }
    if (isset($_SERVER['REQUEST_TIME_FLOAT'])){
     header('X-profiling-between-request-init: '.($this->profiling_stamps->init_start-(float)$_SERVER['REQUEST_TIME_FLOAT']));
     header('X-profiling-all: '.($this->profiling_stamps->real_run_end-(float)$_SERVER['REQUEST_TIME_FLOAT']));
    }else{
     header('X-profiling-all: '.($this->profiling_stamps->real_run_end-$this->profiling_stamps->init_start));
    }
   }

   // Выводим контент
   if ($this->need_prepare_release and $sad_need_output){
    // Пишем header'ы, размер контента
    header('http/1.1 '.$this->http_status);
    if($this->options->content_type!=''){
     header('Content-Type: '.$this->options->content_type);
    }
    header('Content-Length: '.strlen($this->output_buffer));
    echo $this->output_buffer;
   }
  }// run
  
  function ob_start(){
   $this->need_prepare_release=true;
   ob_start();
  }
  
  function ob_end(){
   $this->need_prepare_release=false;
   ob_end_clean();
  }
  
  protected function real_run(){
   global $sad_root, $sad_baseurl, $sad_contype;
   $sad_url        =$this->options->url;
   $sad_root       =$this->options->root;
   $sad_baseurl    =$this->options->baseurl;
   $sad_contype    =$this->options->content_type;
   $sad_old_contype=$sad_contype;

   $this->ob_start();
   $this->profiling_stamps->clear_exec_start=microtime(true);
   require($this->options->root.'/templates/static/'.$this->source_file);
   $this->profiling_stamps->clear_exec_end  =microtime(true);

   if ($sad_contype!=$sad_old_contype){
    $this->options->content_type = $sad_contype;
   }
   unset($sad_old_contype);

   if ($this->need_prepare_release){
    $sad_body = ob_get_contents();
    ob_end_clean();
    $sad_body = preg_replace_callback('_\\{#(wait|prepare|prepared):([a-z0-9]{16,16}):(.*?)#\\}_i',
     'self::sad_parse_prepare', $sad_body);

    // Добавляем
    /* @todo Вернуть, когда будет /i/
     * if ($this->iam() and !$sad_hide and $this->options->content_type=='text/html; charset=utf-8'){
     * $sad_body.='<script src="'.$sad_baseurl.'/i/adminbutton.js"></script>';
     * }
     */

    // Кеширование через E-tag
    // @todo Поправить. Работает неправильно
    if($this->options->etag_cache and ($this->http_status==200)){
     if(!isset($this->etag_hash)){
      $this->etag_hash = sha1($sad_body);
     }
     header('ETag: '.$this->etag_hash);
     header('Cache-Control: must-revalidate');
     header('Pragma: ');
     if(isset($_SERVER['HTTP_IF_NONE_MATCH'])){
      if($_SERVER['HTTP_IF_NONE_MATCH']==$this->etag_hash and $this->etag_hash!=''){
       header('http/1.1 304');
       return;
      }
     }
    }else{
     header('Cache-control: no-cache');
    }

    // Добавляем, если нужно, в html-документ даты и время
    if(!($this->options->hidetimestamp or $this->options->hide_cms)){
     // Текущая дата
     $sad_body .= '<!-- '.gmdate('d.m.Y H:i:sO').' -->';
    }
    if(!($this->options->hidewtime or $this->options->hide_cms)){
     // Время на инициализацию
     $sad_body .= '<!-- '.($this->profiling_stamps->clear_exec_end - $this->profiling_stamps->clear_exec_start).' -->';
    }

    $this->output_buffer=$sad_body;
   }
  }// real run
  
  function iam(){
   return false;
  }
  
  function iamd(){
   return false;
  }

  function error_reporting($new_level, $new_display=null){
   error_reporting($new_level);
   $this->error_reporting_level=$new_level;
   if ($new_display!==null){
    $this->error_display=$new_display;
    ini_set('display_errors', $this->error_display ? 'stdout' : 'stderr');
   }
  }

  function default_error_handler($errorNumber, $message, $errfile, $errline){
   // Обработчик ошибок вместо стандартного
   // HHVM передаст сюда управление в случае фатальной ошибки (н-р, ошибки синтакса), обычный PHP — нет
   $this->error_handled[]=(object)array(
    'code'   =>$errorNumber,
    'message'=>$message,
    'file'   =>$errfile,
    'line'   =>$errline
   );

   // Некоторая часть из этих ошибок не может быть отловлена через error_handler
   switch ($errorNumber) {
    case E_ERROR           : $errorLevel = 'Error';   break;
    case E_WARNING         : $errorLevel = 'Warning'; break;
    case E_PARSE           : $errorLevel = 'Parse error'; break;
    case E_NOTICE          : $errorLevel = 'Notice';  break;
    case E_CORE_ERROR      : $errorLevel = 'Core error';   break;
    case E_CORE_WARNING    : $errorLevel = 'Core warning'; break;
    case E_COMPILE_ERROR   : $errorLevel = 'Compile error';   break;
    case E_COMPILE_WARNING : $errorLevel = 'Compile warning'; break;
    case E_USER_ERROR      : $errorLevel = 'User error';   break;
    case E_USER_WARNING    : $errorLevel = 'User warning'; break;
    case E_USER_NOTICE     : $errorLevel = 'User notice';  break;
    case E_STRICT          : $errorLevel = 'Strict';  break;
    case E_RECOVERABLE_ERROR:$errorLevel = 'Recoverable error';  break;
    case E_DEPRECATED      : $errorLevel = 'Deprecated';  break;
    case E_USER_DEPRECATED : $errorLevel = 'User deprecated';  break;
    case 16777217          : $errorLevel = 'Fatal error [HHVM]';  break;
    default: $errorLevel = 'Undefined ('.$errorNumber.')';
   }

   $this_message='<br/><strong>'.$errorLevel.'</strong>: '.$message.' in <strong>'.$errfile.
    '</strong> on line <strong>'.$errline.'</strong><br/>';
   if ((error_reporting() & $errorNumber == $errorNumber)and($this->error_display)){
    echo $this_message;
   }
   @file_put_contents($this->options->error_log_file,
    gmdate('Y-m-d H:i:sO').' '.$errorLevel.': '.$message.' in '.$errfile.' on line '.$errline."\r\n",
    FILE_APPEND | LOCK_EX);

   // Реагируем на ошибку, если она в списке критических. Да, большая часть из этих ошибок неперехватываема
   if (in_array($errorNumber, array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR,
                                    E_USER_ERROR, E_RECOVERABLE_ERROR))){
    throw new ErrorException($message, 0, $errorNumber, $errfile, $errline);
   }
  }

  function unexpected_shutdown_handler(){
   $error=error_get_last();
   if (($error===null)or($this->real_run_ended)){return;}
   @file_put_contents($this->options->error_log_file,
    gmdate('Y-m-d H:i:sO').' Critical error: '.$error['type'].': '.$error['message'].
    ' in '.$error['file'].' on line '.$error['line']."\r\n",
    FILE_APPEND | LOCK_EX);

   // @todo В зависимости от параметров, пишем в свои логи или вызываем callback-замыкание от наследующегося кода
   // В HHVM error_get_last не будет содержать строки с ошибками, нужно использовать error_handled
   // В обычном PHP error_handled не будет содержать ошибки, вызвавшей это исключение
  }

  // template engine
  function get_template($filename='index.html'){
   // @todo Загрузка из memcache
   if (mb_substr($filename, 0, 0)!='/'){$filename=$this->options->root.'/templates/html/'.$filename;}
   $sad_template =@file_get_contents($filename);
   $i=1; while ($i>0){
    $sad_template=preg_replace_callback('|{#include: *([a-z0-9_.\\/-]+) *#}|i', 'self::get_template_callback', $sad_template, -1, $i);
   }
   
   $sad_template=preg_replace_callback('/\\{#(wait|prepare|prepared):([a-z0-9._-]+) *#\\}/i', 'self::sad_parse_prepare_in', $sad_template);
   return $sad_template;
  }
  
  protected function get_template_callback($a){
   return file_get_contents($this->options->root.'/templates/html/'.$a[1]);
  }
  
  function sad_prepare($key,$value){
   if (!isset($this->prepared[$key])){
    $this->prepared[$key]=array('hash'=>$this->sad_create_hash());
   }
   $this->prepared[$key]['value']=$value;
  }
  
  function sad_create_hash(){
   $hashes=array(0,1,2,3,4,5,6,7,8,9);
   $hash  ='';
   for ($i=ord('a');$i<=ord('z');$i++){
    array_push($hashes,chr($i),strtoupper(chr($i)));
   }
   // Знаете откуда здесь это магическое число? ceil(LOG(3^200)/LOG(62))
   for ($i=0;$i<54;$i++){
    $hash.=$hashes[mt_rand(0,count($hashes)-1)];
   }
   return $hash;
  }
  
  protected function sad_parse_prepare_in($a){
   if (!isset($this->prepared[$a[2]])){
    $hash=$this->sad_create_hash();
    $this->prepared[$a[2]]=array('hash'=>$hash);
   }else{
    $hash=$this->prepared[$a[2]]['hash'];
   }
   return '{'.'#wait:'.$hash.':'.$a[2].'#}';
  }
  
  protected function sad_parse_prepare($a){
   if (!isset($this->prepared[$a[3]])){return $a[0];}
   if ($this->prepared[$a[3]]['hash']!==$a[2]){return $a[0];}
   return $this->prepared[$a[3]]['value'];
  }

  // База данных и SQL
  function database_connect(){
   try{
    $this->database_connection = new AscetPDO(
     'mysql:host='.$this->options->database->host.';dbname='.$this->options->database->db,
     $this->options->database->login,
     $this->options->database->password);
   }catch(PDOException $e){
    switch($this->options->database_connection_error_strategy){
     case self::DATABASE_CONNECTION_ERROR_STRATEGY_MESSAGE:
      echo $e->getMessage();
      exit;
     case self::DATABASE_CONNECTION_ERROR_STRATEGY_PAGE:
      // @todo Имплементировать
      break;
     case self::DATABASE_CONNECTION_ERROR_STRATEGY_NOTHING:
      return false;
    }
   }
   $this->database_connection->exec('SET NAMES utf8;');
   if ($this->options->database_transaction_start){
    $this->database_connection->exec('set session transaction isolation level '.$this->options->database_transaction_level.';');
    $this->database_connection->transaction_level='read committed';
    $this->database_connection->beginTransaction();
   }

   return true;
  }

  function sql_exec($sql){
   try{
    $this->database_connection->sql_exec($sql);
   }catch(PDOException $e){
    switch ($this->options->database_error_strategy){
     case self::DATABASE_ERROR_STRATEGY_WARNING:  echo $e->getMessage(); break;
     case self::DATABASE_ERROR_STRATEGY_EXIT:     header('http/1.1 500');exit;
     case self::DATABASE_ERROR_STRATEGY_EXCEPTION:throw $e;break;
     case self::DATABASE_ERROR_STRATEGY_NOTHING:  break;
    }
   }
  }

  function sql_query($sql){
   try{
    return $this->database_connection->sql_query($sql);
   }catch(PDOException $e){
    switch ($this->options->database_error_strategy){
     case self::DATABASE_ERROR_STRATEGY_WARNING:  echo $e->getMessage(); break;
     case self::DATABASE_ERROR_STRATEGY_EXIT:     header('http/1.1 500');exit;
     case self::DATABASE_ERROR_STRATEGY_EXCEPTION:throw $e;break;
     case self::DATABASE_ERROR_STRATEGY_NOTHING:  break;
    }
    return null;
   }
  }

  function sql_query_fetch($sql){
   return sql_fetch($this->sql_query($sql));
  }

  function sql_insert_id(){
   return $this->database_connection->lastInsertId();
  }

  function sql_next_transaction(){
   $this->database_connection->sql_next_transaction();
  }

  function sql_level($transaction_level=''){
   $this->database_connection->sql_level($transaction_level);
  }

 } // CoreEngine class

 // Global functions
 // base functions
 function sad_safe_domain($domain){
  $s=strtolower($domain);
  $a=explode(':',$s,2);
  $s=$a[0];
  if (preg_match_all('|\\.|',$s,$sad_tmp)>1){
   $s=preg_replace('|^www\\.(.+?)$|','$1',$s);
  }
  return $s;
 }
 
 function sad_safe_reg($text){
  $ar='.-\\/[]{}?+';
  $s=''; for ($i=0;$i<strlen($text);$i++){
   if (strpos(' '.$ar,$text[$i])>0){
    $s.='\\'.$text[$i];
   }else{
    $s.=$text[$i];
   }
  }
  return $s;
 }

 function sad_safe_path($path){
  $a=explode('/',$path);
  $s='';
  foreach ($a as $e){
   if ($e=='' or $e=='.' or $e=='..'){continue;}
   $s.='/'.$e;
  }
  return substr($s,1);
 }
 
 function sad_prepare($key, $value){
  AscetCMSEngine::$last_created_instance->sad_prepare($key, $value);
 }

 // SQL globals functions. Эти функции дёргают последний инстанс AscetCMSEngine
 function sql_exec($sql){
  AscetCMSEngine::$last_created_instance->sql_exec($sql);
 }

 function sql_query($sql){
  return AscetCMSEngine::$last_created_instance->sql_query($sql);
 }

 function sql_query_fetch($sql){
  return AscetCMSEngine::$last_created_instance->sql_query_fetch($sql);
 }

 function sql_insert_id(){
  return AscetCMSEngine::$last_created_instance->sql_insert_id();
 }

 function sql_next_transaction(){
  AscetCMSEngine::$last_created_instance->sql_next_transaction();
 }

 function sql_level($transaction_level=''){
  AscetCMSEngine::$last_created_instance->sql_level($transaction_level);
 }
?>